//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2005
//
// Map view scale handling.
//

# include "StdAfx.h"
# include "Resource.h"
# include "Scale.h"
# include "Position.h"
# include "Map.h"
# include "Status.h"
# include "MapView.h"
# include "PrintUnits.h"
# include "COMViewEvents.h"
# include "fast_floor.h"
# include "Globals.h"

	// The Earth radius in meters.
const float c_ER = 6378137;

const float c_Scale_20km = 20000.f/c_pixels_per_cm;

	// Meters per pixel.
float g_fScale = c_Scale_20km;

	// Pixels per degree.
float g_kX, g_kY;

	// Cos(latitude) in the center of window.
//float g_fCosY = 1;

static
float _GetKx (float _Scale) {return c_ER*(c_PI/180.f)/_Scale*g_map.fCosY;}
static
float _GetKy (float _Scale) {return c_ER*(c_PI/180.f)/_Scale;}

void RefreshScales () {
	CCritSec::CLock lock (g_cs);

//	g_fCosY = ::cos (g_map.rectBound.y0 + g_Y + .5*g_lHeight/g_kY);

	g_kX = _GetKx (g_fScale);
	g_kY = _GetKy (g_fScale);

	g_lX = g_X*g_kX;
	g_lY = g_Y*g_kY;

	g_lMapWidth  = g_map.rectBound.Width  () <= 0 ? g_lWidth  : g_kX*g_map.rectBound.Width ();
	g_lMapHeight = g_map.rectBound.Height () <= 0 ? g_lHeight : g_kY*g_map.rectBound.Height ();
}

float GetRulerLen () {
	if (g_DistanceUnit == unMetric)
		return c_pixels_per_cm;

	const float c_m_per_mile = g_DistanceUnit == unStatute ? c_m_per_smile : c_m_per_nmile;
	return c_pixels_per_cm*c_m_per_mile/1000;
}

float RoundScale (float _f) {
	assert (_f > 0);

	float fK = c_pixels_per_cm;
	if (g_DistanceUnit != unMetric) {
		const float c_m_per_mile = g_DistanceUnit == unStatute ? c_m_per_smile : c_m_per_nmile;
		if (_f < 199/c_pixels_per_cm)
			fK = c_pixels_per_cm*c_m_per_mile/1000*c_feet_per_m;
	}

	const float l = ::log10 (_f*fK);
	assert (l >= 0);
	const float m = ::pow (10.f, ::fmod (l, 1.f));
	const float o = ::pow (10.f, fast_floor (l));
	assert (1 <= m && m < 10);

	const float m2 = fast_floor (m*10 + .5f)*.1f;

	return m2*o/fK;
}

void SetScale (float _fNewScale, bool _bRefresh) {
	SetScale (_fNewScale, g_lWidth/2, g_lHeight/2, _bRefresh);
}

void SetScale (float _fNewScale, long _lX0, long _lY0, bool _bRefresh) {
	if (_fNewScale > c_ScaleMax)
		_fNewScale = c_ScaleMax;
	else if (_fNewScale < c_ScaleMin)
		_fNewScale = c_ScaleMin;

	_fNewScale = RoundScale (_fNewScale);

	if (_fNewScale == g_fScale)
		return;

	SetScaleStatus (_fNewScale);

	const float fNewKx = _GetKx (_fNewScale);
	const float fNewKy = _GetKy (_fNewScale);

	g_X += _lX0/g_kX - _lX0/fNewKx;
	_lY0 = g_lHeight - _lY0;
	g_Y += _lY0/g_kY - _lY0/fNewKy;

	g_fScale = _fNewScale;
	RefreshScales ();

	if (_bRefresh)
		Refresh ();

	Fire_OnScaleChange ();
}

float GetRectScale (const rect_t & _rect) {
	const float fScale1 = c_ER*(c_PI/180.f)*_rect.Width  ()/(g_lWidth  > c_iMargin*2 ? g_lWidth  - c_iMargin*2 : c_iMargin*2)*g_map.fCosY;
	const float fScale2 = c_ER*(c_PI/180.f)*_rect.Height ()/(g_lHeight > c_iMargin*2 ? g_lHeight - c_iMargin*2 : c_iMargin*2);
	return fScale1 > fScale2 ? fScale1 : fScale2;
}

float GetFullMapScale () {
	CCritSec::CLock lock (g_cs);
	return GetRectScale (g_map.rectBound);
}

void SetFullMapScale () {
	g_fScale = GetFullMapScale ();
	SetScaleStatus (g_fScale);
	RefreshScales ();

	{
		CCritSec::CLock lock (g_cs);
		g_X = g_map.rectBound.Width ()/2  - g_lWidth/(2*g_kX);
		g_Y = g_map.rectBound.Height ()/2 - g_lHeight/(2*g_kY);
	}
	RefreshScales ();

	Refresh ();
}

float GetScale (float _fScale, char * _strText) {
	assert (_fScale > 0);

	switch (g_DistanceUnit) {
		default:
			assert (false);

		case unMetric: {
			const bool bInKm = _fScale*c_pixels_per_cm >= 1000;
			const float fValue = bInKm ? _fScale*c_pixels_per_cm/1000 : _fScale*c_pixels_per_cm;

			// Get the ruler text.
			const int iValue_int = fast_floor (fValue + .001f);
			const int iValue_rat = fast_floor (::fmod (fValue + 0.01f, 1)*10 + .5f);

			if (iValue_rat == 0)
				::sprintf (_strText, "%d %s",    iValue_int,             bInKm ? GetStr (IDS_UNIT_KM) : GetStr (IDS_UNIT_M));
			else
				::sprintf (_strText, "%d.%d %s", iValue_int, iValue_rat, bInKm ? GetStr (IDS_UNIT_KM) : GetStr (IDS_UNIT_M));

			return c_pixels_per_cm;
		}

		case unNautical:
		case unStatute: {
			const bool bSMile = g_DistanceUnit == unStatute;
			const char * const strMile = GetStr (bSMile ? IDS_UNIT_SMILE : IDS_UNIT_NMILE);
			const float c_m_per_mile = bSMile ? c_m_per_smile : c_m_per_nmile;
			const float fRulerLen = c_pixels_per_cm*c_m_per_mile/1000;

			const float fMileScale = _fScale/c_m_per_mile;
			const float fFootScale = _fScale*c_feet_per_m;

			const bool bInMiles = fMileScale*fRulerLen >= 0.1999;
			const float fValue = bInMiles ? fMileScale*fRulerLen : fFootScale*fRulerLen;

			// Get the ruler text.
			if (fValue < 0.999) {
				const int nValueBy100 = fast_floor (fValue*100 + .5f);
				if ((nValueBy100 % 10) == 0)
					::sprintf (_strText, "0.%d %s", nValueBy100/10, bInMiles ? strMile : GetStr (IDS_UNIT_FT));
				else
					::sprintf (_strText, "0.%02d %s", nValueBy100,  bInMiles ? strMile : GetStr (IDS_UNIT_FT));
			} else if (fValue < 9.99) {
				const int nValueBy10 = fast_floor (fValue*10 + .5f);
				const int nValue_int = nValueBy10/10;
				const int nValue_rat = nValueBy10 - nValue_int*10;

				if (nValue_rat == 0)
					::sprintf (_strText, "%d %s",    nValue_int,             bInMiles ? strMile : GetStr (IDS_UNIT_FT));
				else
					::sprintf (_strText, "%d.%d %s", nValue_int, nValue_rat, bInMiles ? strMile : GetStr (IDS_UNIT_FT));
			} else
				::sprintf     (_strText, "%d %s", fast_floor (fValue + .5f), bInMiles ? strMile : GetStr (IDS_UNIT_FT));

			return fRulerLen;
		}
	}
}

/////////////////////////////////////////////////

struct std_scale_t {
	const char * strName;
	float fScale;
	bool bSmallUnits;
};

static
std_scale_t g_std_scales_metric [] = {
	{"10", 10.f, true},
	{"15", 15.f, true},
	{"20", 20.f, true},
	{"30", 30.f, true},
	{"50", 50.f, true},
	{"70", 70.f, true},
	{NULL, 0},
	{"100", 100.f, true},
	{"150", 150.f, true},
	{"200", 200.f, true},
	{"300", 300.f, true},
	{"500", 500.f, true},
	{"700", 700.f, true},
	{NULL, 0},
	{"1",   1000.f, false},
	{"1.5", 1500.f, false},
	{"2",   2000.f, false},
	{"3",   3000.f, false},
	{"5",   5000.f, false},
	{"7",   7000.f, false},
	{NULL, 0},
	{"10",  10000.f, false},
	{"15",  15000.f, false},
	{"20",  20000.f, false},
	{"30",  30000.f, false},
	{"50",  50000.f, false},
	{"70",  70000.f, false},
	{NULL, 0},
	{"100", 100000.f, false},
	{"150", 150000.f, false},
	{"200", 200000.f, false},
	{"300", 300000.f, false},
	{"500", 500000.f, false},
	{"700", 700000.f, false},
	{NULL, 0},
	{"1000", 1000000.f, false},
	{"1500", 1500000.f, false},
	{"2000", 2000000.f, false},
	{"3000", 3000000.f, false},
};

static
std_scale_t g_std_scales_miles [] = {
	{"20",  20.f, true},
	{"30",  30.f, true},
	{"50",  50.f, true},
	{"80",  80.f, true},
	{NULL, 0},
	{"120", 120.f, true},
	{"200", 200.f, true},
	{"300", 300.f, true},
	{"500", 500.f, true},
	{"800", 800.f, true},
	{NULL, 0},
	{"0.2", 0.2f, false},
	{"0.3", 0.3f, false},
	{"0.5", 0.5f, false},
	{"0.7", 0.7f, false},
	{NULL, 0},
	{"1",   1.f,  false},
	{"1.5", 1.5f, false},
	{"2.5", 2.5f, false},
	{"4",   4.f,  false},
	{"7",   7.f,  false},
	{NULL, 0},
	{"10",  10.f, false},
	{"15",  15.f, false},
	{"25",  25.f, false},
	{"40",  40.f, false},
	{"70",  70.f, false},
	{NULL, 0},
	{"100", 100.f, false},
	{"150", 150.f, false},
	{"250", 250.f, false},
	{"400", 400.f, false},
	{"700", 700.f, false},
	{NULL, 0},
	{"1000", 1000.f, false},
	{"1500", 1500.f, false},
};

size_t GetStdScales () {
	if (g_DistanceUnit == unMetric)
		return sizeof (g_std_scales_metric)/sizeof (g_std_scales_metric [0]);
	else
		return sizeof (g_std_scales_miles)/sizeof (g_std_scales_miles [0]);
}

bool GetStdScaleName (size_t _cIndex, char * _strScale) {
	assert (_cIndex < GetStdScales ());
	if (g_DistanceUnit == unMetric) {
		if (g_std_scales_metric [_cIndex].strName == NULL)
			return false;

		const char * const strUnit = GetStr (g_std_scales_metric [_cIndex].bSmallUnits ? IDS_UNIT_M : IDS_UNIT_KM);
		::sprintf (_strScale, "%s %s", g_std_scales_metric [_cIndex].strName, strUnit);
		return true;
	} else {
		if (g_std_scales_miles [_cIndex].strName == NULL)
			return false;

		const char * const strUnit = GetStr (
			g_std_scales_miles [_cIndex].bSmallUnits ? 
				IDS_UNIT_FT
			  :
				(g_DistanceUnit == unStatute ? IDS_UNIT_SMILE : IDS_UNIT_NMILE)
		);
		::sprintf (_strScale, "%s %s", g_std_scales_miles [_cIndex].strName, strUnit);
		return true;
	}
}

float GetStdScale (size_t _cIndex) {
	assert (_cIndex < GetStdScales ());

	const float fRulerLen = GetRulerLen ();

	if (g_DistanceUnit == unMetric) {
		assert (g_std_scales_metric [_cIndex].strName != NULL);
		return g_std_scales_metric [_cIndex].fScale/fRulerLen;
	} else {
		assert (g_std_scales_miles [_cIndex].strName != NULL);

		if (g_std_scales_miles [_cIndex].bSmallUnits)
			return g_std_scales_miles [_cIndex].fScale/c_feet_per_m/fRulerLen;
		else {
			const float c_m_per_mile = g_DistanceUnit == unStatute ? c_m_per_smile : c_m_per_nmile;
			return g_std_scales_miles [_cIndex].fScale*c_m_per_mile/fRulerLen;
		}
	}
}